package profile;

import ij.IJ;

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.beans.PropertyChangeEvent;
import java.io.File;

import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JFileChooser;
import javax.swing.JFormattedTextField;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JSeparator;
import javax.swing.JSpinner;
import javax.swing.SpinnerNumberModel;
import javax.swing.SpringLayout;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;

import util.ExtensionFileFilter;
import util.SpringUtilities;
import view.IView;
import controller.ProfileController;

/**
 * This class is a JPanel that is on the left hand side of the ProfilePanel. It
 * is a view for controlling the number of MADs shown on the profile graph. It
 * also displays the mean and MAD/mean
 * 
 */
public class ProfileResultLeftPanel extends JPanel implements IView {

    private static final long serialVersionUID = 717747672086520073L;

    private Profile profile;
    ProfileResultFrame profileResultFrame;

    private JLabel madSpinnerLabel = new JLabel("Peak Definition Threshold (MADs)", JLabel.TRAILING);;
    private JSpinner madSpinner;
    private SpinnerNumberModel madSpinnerModel;
    
    private JLabel dbThresholdSpinnerLabel = new JLabel("dB Threshold");
    private JSpinner dbSpinner;
    private SpinnerNumberModel dbSpinnerModel;

    private JLabel madLabel = new JLabel("MAD");
    private JLabel meanLabel = new JLabel("Mean");
    private JLabel madMeanLabel = new JLabel("MAD/mean");
    private JLabel dynamicRangeLabel = new JLabel("Dynamic Range (db)");
    private JLabel numElementsLabel = new JLabel("Number of Elements");
    private JFormattedTextField madMeanValueField;
    private JFormattedTextField madValueField;
    private JFormattedTextField meanValueField;
    private JFormattedTextField dynamicRangeValueField;
    private JFormattedTextField numElementsValueField;

    private JCheckBox dynamicRangeCheckBox = new JCheckBox();
    private JCheckBox numElementsCheckBox = new JCheckBox();

    // export stuff
    private JButton exportToExcelButton = new JButton("Export Profile to Excel");
    
    // filter stuff
    private JButton lowPassFilterButton = new JButton("Low Pass Filter");
    private JButton lowPassFilterResetButton = new JButton("Reset");
    JSpinner filterRadiusSpinner;
    private JLabel filterRadiusSpinnerLabel = new JLabel("Radius", JLabel.TRAILING);
    int chartSelectionX;
    int chartSelectionWidth;
    
    // crop stuff
    //TODO: add crop icon to button?
    private JButton cropButton = new JButton("Crop");
    private JButton resetCropButton = new JButton("Reset");

    public ProfileResultLeftPanel(Profile profile, int startingMADs, ProfileResultFrame profileResultFrame) {
        this.profile = profile;
        this.profileResultFrame = profileResultFrame;

        profile.getController().addView(this);

        initComponents(startingMADs);
    }

    private void initComponents(int startingMADs) {
        madLabel.setLabelFor(madValueField);
        madValueField = new JFormattedTextField(new Double(profile.getProfileMAD()));
        madValueField.setEditable(false);

        meanLabel.setLabelFor(meanValueField);
        meanValueField = new JFormattedTextField(new Double(profile.getProfileMean()));
        meanValueField.setEditable(false);

        madMeanLabel.setLabelFor(madMeanValueField);
        madMeanValueField = new JFormattedTextField(new Double(profile.getProfileMAD()
                / profile.getProfileMean()));
        madMeanValueField.setEditable(false);

        dynamicRangeLabel.setLabelFor(dynamicRangeValueField);
        dynamicRangeValueField = new JFormattedTextField(new Long(60));
        dynamicRangeValueField.setEditable(true);
        dynamicRangeValueField.setEnabled(false);

        numElementsLabel.setLabelFor(numElementsValueField);
        numElementsValueField = new JFormattedTextField(new Long(128));
        numElementsValueField.setEditable(true);
        numElementsValueField.setEnabled(false);

        initCheckBoxes();

        initMadComponents(startingMADs);
        initDBThresholdComponents();
        initFilterComponents();
        initCropComponents();

        this.setLayout(new SpringLayout());

        //adding empty jlabels because spring layout needs something for the empty spots
        this.add(new JLabel());         this.add(madLabel);                 this.add(madValueField);
        this.add(new JLabel());         this.add(meanLabel);                this.add(meanValueField);
        this.add(new JLabel());         this.add(madMeanLabel);             this.add(madMeanValueField);
        this.add(dynamicRangeCheckBox); this.add(dynamicRangeLabel);        this.add(dynamicRangeValueField);
        this.add(numElementsCheckBox);  this.add(numElementsLabel);         this.add(numElementsValueField);
        this.add(new JLabel());         this.add(madSpinnerLabel);          this.add(madSpinner);
        this.add(new JLabel());         this.add(dbThresholdSpinnerLabel);  this.add(dbSpinner);
        this.add(new JLabel());         this.add(new JSeparator(JSeparator.HORIZONTAL)); this.add(new JLabel()); 
        this.add(new JLabel());         this.add(lowPassFilterButton);      this.add(new JLabel());
        this.add(new JLabel());         this.add(filterRadiusSpinnerLabel); this.add(filterRadiusSpinner);
        this.add(new JLabel());         this.add(new JLabel());             this.add(lowPassFilterResetButton);
        this.add(new JLabel());         this.add(new JSeparator(JSeparator.HORIZONTAL)); this.add(new JLabel());
        this.add(new JLabel());         this.add(cropButton);               this.add(new JLabel());
        this.add(new JLabel());         this.add(new JLabel());             this.add(resetCropButton);
        this.add(new JLabel());         this.add(new JSeparator(JSeparator.HORIZONTAL)); this.add(new JLabel()); 
        this.add(new JLabel());         this.add(exportToExcelButton);      this.add(new JLabel()); 
        
        initExportButton();
        /*
         * Whenever adding new rows or columns to this panel it's necessary to update the numbers below
         */
        SpringUtilities.makeCompactGrid(this,
                16, 3,   // rows, cols
                6, 6,    // initX, initY
                6, 6);   // xPad, yPad
        
    }
    
    private void initMadComponents(int startingM) {
        madSpinnerModel = new SpinnerNumberModel(startingM, 0, 50, 1);
        madSpinner = new JSpinner(madSpinnerModel);
        madSpinner.addChangeListener(new ChangeListener() {
            @Override
            public void stateChanged(ChangeEvent e) {
                madSpinnerStateChanged(e);
            }
        });

        madSpinnerLabel.setLabelFor(madSpinner);
    }
    
    private void initDBThresholdComponents() {
        dbSpinnerModel = new SpinnerNumberModel(new Float(3.0), new Float(0.0), new Float(100.0), new Float(1.0));
        dbSpinner = new JSpinner(dbSpinnerModel);
        dbThresholdSpinnerLabel.setLabelFor(dbSpinner);
        
        //set it to be disabled initially because it requires the user to input dB range
        dbSpinner.setEnabled(false);
    }

    /**
     * Initializes components for low pass filter functionality
     */
    private void initFilterComponents() {
        lowPassFilterButton.setEnabled(false);
        lowPassFilterResetButton.setEnabled(false);
        filterRadiusSpinner = new JSpinner(new SpinnerNumberModel(2.0, 0.0, 100, 0.1));
        filterRadiusSpinner.setEnabled(false);
        filterRadiusSpinnerLabel.setLabelFor(filterRadiusSpinner);
        
        lowPassFilterButton.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                doLowPassFilter(chartSelectionX, chartSelectionWidth);
                lowPassFilterResetButton.setEnabled(true);
            }
        });
        lowPassFilterResetButton.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                doFilterReset();
                lowPassFilterResetButton.setEnabled(false);
            }
        });
    }
    
    private void doLowPassFilter(int x, int width) {
        correctChartSelectionParams();
        profile.performLowPassFilter(x, width, (Double) filterRadiusSpinner.getValue());
        
        profileResultFrame.updateProfilePanel();
        profile.getController().changeChartSelectionPresent(false);
    }
    
    private void doFilterReset() {
        profile.resetLowPassFilter();
        profileResultFrame.updateProfilePanel();
        profile.getController().changeChartSelectionPresent(false);
    }
    
    /**
     * Initializes components for crop functionality
     */
    private void initCropComponents() {
        cropButton.setEnabled(false);
        cropButton.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                doCrop(chartSelectionX, chartSelectionWidth);
                resetCropButton.setEnabled(true);
            }
        });
        
        resetCropButton.setEnabled(false);
        resetCropButton.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                doCropReset();
                resetCropButton.setEnabled(false);
            }
        });
    }
    


    /**
     * Performs a "crop" on the profile.  In a profile crop, the cropped data
     * is left in place but ignored when doing calculations (such as median,
     * MAD, etc.
     * 
     * @param x
     * @param width
     */
    private void doCrop(int x, int width) {
        correctChartSelectionParams();
        profile.cropProfile(x, width);
        
        profileResultFrame.updateProfilePanel();
        profile.getController().changeChartSelectionPresent(false);
        updateProfileCalculations();
    }
    
    /**
     * Resets the profile so no crop is done 
     */
    private void doCropReset() {
        profile.resetCrop();
        profileResultFrame.updateProfilePanel();
        profile.getController().changeChartSelectionPresent(false);
        updateProfileCalculations();
    }
    
    private void updateProfileCalculations() {
        madValueField.setValue(profile.getProfileMAD());
        meanValueField.setValue(profile.getProfileMean());
        madMeanValueField.setValue(profile.getProfileMAD()
                / profile.getProfileMean());
        
    }
    
    /**
     * "Fixes up" chart selection params.  If the chartSelection X param is less
     * than 0, it sets it to 0.  If the chartSelectionWidth is greater than the
     * profile width, it corrects it to be the profile width - chartSelectionX
     */
    void correctChartSelectionParams() {
        if (chartSelectionX < 0) {
            chartSelectionX = 0;
        }
        if (chartSelectionX + chartSelectionWidth > profile.getWidth()) {
            chartSelectionWidth = profile.getWidth() - chartSelectionX;
        }
    }

    private void initCheckBoxes() {
        dynamicRangeCheckBox.addItemListener(new ItemListener() {
            @Override
            public void itemStateChanged(ItemEvent e) {
                if (e.getStateChange() == ItemEvent.SELECTED) {
                    dynamicRangeValueField.setEnabled(true);
                    dbSpinner.setEnabled(true);
                } else {
                    dynamicRangeValueField.setEnabled(false);
                    dbSpinner.setEnabled(false);
                }
                validate();
                repaint();
            }
        });
        numElementsCheckBox.addItemListener(new ItemListener() {
            @Override
            public void itemStateChanged(ItemEvent e) {
                if (e.getStateChange() == ItemEvent.SELECTED) {
                    numElementsValueField.setEnabled(true);
                } else {
                    numElementsValueField.setEnabled(false);
                }
                validate();
                repaint();
            }
        });
    }

    private void initExportButton() {
        
        final JFileChooser fileChooser = new JFileChooser();
        final ExtensionFileFilter excelFilter = new ExtensionFileFilter("Excel spreadsheet (.xls)","xls");
        
        exportToExcelButton.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                fileChooser.setFileFilter(excelFilter);

                String suggestedFilename = profile.getSuggestedFilename();
                fileChooser.setSelectedFile(new File(suggestedFilename));

                int returnVal = fileChooser.showSaveDialog(ProfileResultLeftPanel.this);
                if (returnVal == JFileChooser.APPROVE_OPTION) {

                    String filename = fileChooser.getSelectedFile().toString();
                    if (!filename.endsWith(".xls")) {
                        filename += ".xls";
                    }
                    File file = new File(filename);

                    // Check if file already exists and prompt user if OK to
                    // overwrite
                    if (overwiteExistingFile(file)) {
                        try {
                            long dRange = -1;
                            long numEls = -1;
                            if (dynamicRangeCheckBox.isSelected()) {
                                dRange = (Long) dynamicRangeValueField.getValue();
                            }
                            if (numElementsCheckBox.isSelected()) {
                                numEls = (Long) numElementsValueField.getValue();
                            }
                            
                            float dbThreshold = 0;
                            if (dbSpinner.isEnabled()) {
                                dbThreshold = (Float) dbSpinner.getValue();
                            }
                            
                            ProfileExporter exporter = new ProfileExporter(profile,
                                    (Integer) madSpinner.getValue(), file, profileResultFrame
                                            .getProfileChart(), dRange, numEls, dbThreshold);
                            ExportBusyDialog busyDialog = new ExportBusyDialog("Generating Excel Spreadsheet...", exporter);
                            busyDialog.Show();
                            busyDialog.doBusy();
                        } catch (Exception ex) {
                            IJ.error("Export failed: " + ex.getLocalizedMessage());
                            return;
                        }
                    }
                }
            }
        });
    }

    /**
     * Checks to see if the file exists. If the file already exists, then it
     * prompts the user that this will overwite the existing file if the user
     * wants to proceed. If the user hits the cancel button, this returns false.
     * If the file does not exist or the user pushes OK, it will return true
     * 
     * @param file
     * @return
     */
    private boolean overwiteExistingFile(File file) {
        if (file.exists()) {
            int res = JOptionPane.showConfirmDialog(null, "File " + file.getName()
                    + " already exists.  Click\n" + "OK to overwrite and save.", "File exists",
                    JOptionPane.OK_CANCEL_OPTION, JOptionPane.PLAIN_MESSAGE);
            if (res == JOptionPane.CANCEL_OPTION) {
                return false;
            }
        }
        return true;
    }

    protected void madSpinnerStateChanged(ChangeEvent e) {
        profile.getController().changeProfileMad((Integer) madSpinner.getValue());
    }

    @Override
    public void modelPropertyChange(PropertyChangeEvent evt) {
        if (evt.getPropertyName().equals(ProfileController.PROFILE_MAD_PROPERTY)) {
            Integer newValue = (Integer) evt.getNewValue();
            if (!madSpinner.getValue().equals(newValue)) {
                madSpinner.setValue(newValue);
            }

        } else if (evt.getPropertyName().equals(ProfileController.CHART_SELECTION_PRESENT)) {
            boolean newValue = (Boolean) evt.getNewValue();
            if (newValue) {
                lowPassFilterButton.setEnabled(true);
                filterRadiusSpinner.setEnabled(true);
                cropButton.setEnabled(true);
            } else {
                lowPassFilterButton.setEnabled(false);
                filterRadiusSpinner.setEnabled(false);
                cropButton.setEnabled(false);
            }
        } else if (evt.getPropertyName().equals(ProfileController.CHART_SELECTION_X)) {
            chartSelectionX = (Integer) evt.getNewValue();
        } else if (evt.getPropertyName().equals(ProfileController.CHART_SELECTION_WIDTH)) {
            chartSelectionWidth = (Integer) evt.getNewValue();
        }

    }

}
